﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using OCR.TesseractWrapper;
using PMC;
using PMC.Properties;

namespace CashCrusaderScript
{
    /// <summary>
    /// CAPTCHA-image internally uses FastBitmap for fast image processing.
    /// </summary>
    public class CaptchaImage : IDisposable
    {
        public FastBitmap Image { get; set; }

        private const double MinMatch = .9; //90%
        private const int MinPixelsForCharacter = 15;
        private static readonly string tessFolder = Path.Combine(Global.AssemblyDirectory, Settings.Default.PluginFolder) + @"\";
        private static readonly Color BackgroundColor = Color.FromArgb(252, 252, 252);
        private bool[,] foundPixels;
        private string solution;
        // Processor for OCR
        private readonly TesseractProcessor _processor = new TesseractProcessor();

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "CaptchaImage implements Dispose(), FastBitmap will take care of the bitmap")]
        public CaptchaImage(Image image)
        {
            var bmp = image as Bitmap ?? new Bitmap(image);
            Image = new FastBitmap(bmp);
            foundPixels = new bool[bmp.Width, bmp.Height];

            // Initiate processor
            if (!_processor.Init(tessFolder, "eng", 3))
                throw new FileNotFoundException("Unable to find traineddata for tesseract");

            // Set whitelist, numeric only
            _processor.SetVariable("tessedit_char_whitelist", "0123456789");
            // One character
            _processor.SetPageSegMode(ePageSegMode.PSM_SINGLE_CHAR);
        }

        public string GetSolution()
        {
            if (solution != null)
                return solution;

            StringBuilder strBuilder = new StringBuilder();
            var images = ClearImage(Image);
             
            foreach (var bmp in images)
            {
                using (var bitmap = bmp.AsBitmap())
                {
                    strBuilder.Append(_processor.Recognize(bitmap));
                }
            }
            var str = strBuilder.ToString().Replace('\n', ' ').Replace(" ", string.Empty);
            Log.Instance.AddMessage(string.Format("Recognized solution: {0}", str));

            return solution = str;
        }

        private static void CleanLines(FastBitmap image)
        {
            // Searches for vertical lines
            int[] verticalArray = new int[image.Width];
            for (int y = 0; y < image.Height; y++)
            {
                for (int x = 0; x < image.Width; x++)
                {
                    if (!Colour.IsBG(image.GetPixel(x, y)))
                    {
                        verticalArray[x]++;
                    }
                }
            }

            // Removes vertical lines
            for (int x = 0; x < verticalArray.Length; x++)
            {
                if (verticalArray[x] > image.Width * MinMatch)
                {
                    for (int y = 0; y < image.Width; y++)
                        image.SetPixel(x, y, Colour.AverageColor(image.GetPixel(x + ((x == 0) ? 1 : -1), y), image.GetPixel(x + ((x == 59) ? -1 : 1), y)));
                }
            }

            // Searches for horizontal lines
            int[] horizontalArray = new int[image.Height];
            for (int x = 0; x < image.Height; x++)
            {
                for (int y = 0; y < image.Width; y++)
                {
                    if (!Colour.IsBG(image.GetPixel(x, y)))
                    {
                        horizontalArray[y]++;
                    }
                }
            }


            // Removes horizontal lines
            for (int y = 0; y < horizontalArray.Length; y++)
            {
                if (horizontalArray[y] > image.Width * MinMatch)
                {
                    for (int x = 0; x < image.Width; x++)
                        image.SetPixel(x, y, Colour.AverageColor(image.GetPixel(x, y + ((y == 0) ? 1 : -1)), image.GetPixel(x, y + ((y == 59) ? -1 : 1))));
                }
            }
        }

        private IEnumerable<FastBitmap> ClearImage(FastBitmap workingImage)
        {
            workingImage.LockImage();

            foundPixels = new bool[workingImage.Width,workingImage.Height];
            //var possibleCharacters = new List<FastBitmap>(4);
            CleanLines(workingImage);

            for (int x = 1; x < workingImage.Width - 1; x++)
            {
                for (int y = 1; y < workingImage.Height - 1; y++)
                {
                    Color pixel = workingImage.GetPixel(x, y);
                    Color west = workingImage.GetPixel(x - 1, y);
                    Color east = workingImage.GetPixel(x + 1, y);
                    Color north = workingImage.GetPixel(x, y - 1);
                    Color south = workingImage.GetPixel(x, y + 1);

                    if (Colour.IsDark(pixel) && Colour.IsDifferent(pixel, west) && Colour.IsDifferent(pixel, north) && Colour.IsDifferent(pixel, east) && Colour.IsDifferent(pixel, south))
                    {
                        workingImage.SetPixel(x, y, BackgroundColor);
                    }

                    if (Colour.IsBright(pixel) && !(Colour.IsDifferent(pixel, west) || Colour.IsDifferent(pixel, north) || Colour.IsDifferent(pixel, east) || Colour.IsDifferent(pixel, south)))
                    {
                        workingImage.SetPixel(x, y, BackgroundColor);
                    }

                    if (Colour.IsBright(pixel) && Colour.IsDifferent(pixel, west) && Colour.IsDifferent(pixel, north) && Colour.IsDifferent(pixel, east) && Colour.IsDifferent(pixel, south))
                    {
                        workingImage.SetPixel(x, y, Colour.AverageColor(Colour.AverageColor(south, west), Colour.AverageColor(north, south)));
                    }
                }
            }

            workingImage.UnlockImage();
            // Search for characters
            for (int x = 1; x < workingImage.Width - 1; x++)
            {
                for (int y = 1; y < workingImage.Height - 1; y++)
                {
                    var possibleCharacterPoints = new LinkedList<Point>();
                    workingImage.LockImage();
                    FindPixels(x, y, workingImage, possibleCharacterPoints);
                    workingImage.UnlockImage();
                    // Checks if the number of pixels are greater than the minimum for a character
                    // Smaller than: make them the same color as background
                    if (possibleCharacterPoints.Count < MinPixelsForCharacter)
                    {
                        workingImage.LockImage();
                        foreach (Point p in possibleCharacterPoints)
                            workingImage.SetPixel(p.X, p.Y, BackgroundColor);
                        workingImage.UnlockImage();
                    }
                    //Greater than: Is character
                    else
                    {
                        int lowestX = workingImage.Width;
                        int lowestY = workingImage.Height;
                        int highestX = 0;
                        int highestY = 0;
                        foreach (Point p in possibleCharacterPoints)
                        {
                            if (p.X < lowestX)
                                lowestX = p.X;
                            if (p.Y < lowestY)
                                lowestY = p.Y;
                            if (p.X > highestX)
                                highestX = p.X;
                            if (p.Y > highestY)
                                highestY = p.Y;
                        }
                        //Create image of the character
                        FastBitmap b = workingImage.Clone(new Rectangle(lowestX, lowestY, highestX - lowestX, highestY - lowestY), System.Drawing.Imaging.PixelFormat.DontCare);
                        yield return b;
                        //possibleCharacters.Add(b);
                    }
                }
            }
           // return possibleCharacters;
        }

        /// <summary>
        /// Searches for pixels that looks almost the same
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="workingImage">image</param>
        /// <param name="list">List to add</param>
        private void FindPixels(int x, int y, FastBitmap workingImage, LinkedList<Point> list)
        {
            if (!foundPixels[x, y])
            {
                foundPixels[x, y] = true; //Marks the pixel as found
                Color color = workingImage.GetPixel(x, y);

                if (!Colour.IsBG(color))
                {
                    list.AddLast(new Point(x, y));

                    if (x > 0)
                        FindPixels(x - 1, y, workingImage, list);
                    if (x < workingImage.Width - 1)
                        FindPixels(x + 1, y, workingImage, list);
                    if (y > 0)
                        FindPixels(x, y - 1, workingImage, list);
                    if (y < workingImage.Height - 1)
                        FindPixels(x, y + 1, workingImage, list);
                }
            }
        }

        private void DisposeMe()
        {
            Image.Dispose();
            _processor.Dispose();
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                DisposeMe();
            }
        }

        public void Dispose()
        {
            DisposeMe();
            GC.SuppressFinalize(this);
        }

        ~CaptchaImage()
        {
            DisposeMe();
        }
    }
}
